Timer
Introduction
A timer module is probably one of the most basic pieces of hardware. But even for a timer, there are some interesting things that you can do with SpinalHDL. This example will define a simple timer component which integrates a bus bridging utile.
Timer
So let’s start with the Timer
component.
Specification
The Timer
component will have a single construction parameter:
Parameter Name |
Type |
Description |
---|---|---|
width |
Int |
Specify the bit width of the timer counter |
And also some inputs/outputs:
IO Name |
Direction |
Type |
Description |
---|---|---|---|
tick |
in |
Bool |
When |
clear |
in |
Bool |
When |
limit |
in |
UInt(width bits) |
When the timer value is equal to |
full |
out |
Bool |
|
value |
out |
UInt(width bits) |
Wire out the timer counter value. |
Implementation
case class Timer(width : Int) extends Component {
val io = new Bundle {
val tick = in Bool()
val clear = in Bool()
val limit = in UInt(width bits)
val full = out Bool()
val value = out UInt(width bits)
}
val counter = Reg(UInt(width bits))
when(io.tick && !io.full) {
counter := counter + 1
}
when(io.clear) {
counter := 0
}
io.full := counter === io.limit && io.tick
io.value := counter
}
Bridging function
Now we can start with the main purpose of this example: defining a bus bridging function. To do that we will use two techniques:
Using the
BusSlaveFactory
tool documented hereDefining a function inside the
Timer
component which can be called from the parent component to drive theTimer
‘s IO in an abstract way.
Specification
This bridging function will take the following parameters:
Parameter Name |
Type |
Description |
---|---|---|
busCtrl |
BusSlaveFactory |
The |
baseAddress |
BigInt |
The base address where the bridging logic should be mapped. |
ticks |
Seq[Bool] |
A list of Bool sources that can be used as a tick signal. |
clears |
Seq[Bool] |
A list of Bool sources that can be used as a clear signal. |
The register mapping assumes that the bus system is 32 bits wide:
Name |
Access |
Width |
Address offset |
Bit offset |
Description |
---|---|---|---|---|---|
ticksEnable |
RW |
len(ticks) |
0 |
0 |
Each |
clearsEnable |
RW |
len(clears) |
0 |
16 |
Each |
limit |
RW |
width |
4 |
0 |
Access the limit value of the timer component.
When this register is written to, the timer is cleared.
|
value |
R |
width |
8 |
0 |
Access the value of the timer. |
clear |
W |
8 |
When this register is written to, it clears the timer. |
Implementation
Let’s add this bridging function inside the Timer
component.
case class Timer(width : Int) extends Component {
...
// The function prototype uses Scala currying funcName(arg1,arg2)(arg3,arg3)
// which allow to call the function with a nice syntax later
// This function also returns an area, which allows to keep names of inner signals in the generated VHDL/Verilog.
def driveFrom(busCtrl: BusSlaveFactory, baseAddress: BigInt)(ticks: Seq[Bool], clears: Seq[Bool]) = new Area {
// Offset 0 => clear/tick masks + bus
val ticksEnable = busCtrl.createReadAndWrite(Bits(ticks.length bits), baseAddress + 0,0) init(0)
val clearsEnable = busCtrl.createReadAndWrite(Bits(clears.length bits), baseAddress + 0,16) init(0)
val busClearing = False
io.clear := (clearsEnable & clears.asBits).orR | busClearing
io.tick := (ticksEnable & ticks.asBits ).orR
// Offset 4 => read/write limit (+ auto clear)
busCtrl.driveAndRead(io.limit, baseAddress + 4)
busClearing.setWhen(busCtrl.isWriting(baseAddress + 4))
// Offset 8 => read timer value / write => clear timer value
busCtrl.read(io.value, baseAddress + 8)
busClearing.setWhen(busCtrl.isWriting(baseAddress + 8))
}
}
Usage
Here is some demonstration code which is very close to the one used in the Pinsec SoC timer module. Basically it instantiates following elements:
One 16 bit prescaler
One 32 bit timer
Three 16 bit timers
Then by using an Apb3SlaveFactory
and functions defined inside the Timer
s, it creates bridging logic between the APB3 bus and all instantiated components.
val io = new Bundle {
val apb = slave(Apb3(Apb3Config(addressWidth=8, dataWidth=32)))
val interrupt = out Bool()
val external = new Bundle {
val tick = in Bool()
val clear = in Bool()
}
}
// Prescaler is very similar to the timer, it mainly integrates a piece of auto reload logic.
val prescaler = Prescaler(width = 16)
val timerA = Timer(width = 32)
val timerB,timerC,timerD = Timer(width = 16)
val busCtrl = Apb3SlaveFactory(io.apb)
prescaler.driveFrom(busCtrl, 0x00)
timerA.driveFrom(busCtrl, 0x40)(
ticks=List(True, prescaler.io.overflow),
clears=List(timerA.io.full)
)
timerB.driveFrom(busCtrl, 0x50)(
ticks=List(True, prescaler.io.overflow, io.external.tick),
clears=List(timerB.io.full, io.external.clear)
)
timerC.driveFrom(busCtrl, 0x60)(
ticks=List(True, prescaler.io.overflow, io.external.tick),
clears=List(timerC.io.full, io.external.clear)
)
timerD.driveFrom(busCtrl, 0x70)(
ticks=List(True, prescaler.io.overflow, io.external.tick),
clears=List(timerD.io.full, io.external.clear)
)
val interruptCtrl = InterruptCtrl(4)
interruptCtrl.driveFrom(busCtrl, 0x10)
interruptCtrl.io.inputs(0) := timerA.io.full
interruptCtrl.io.inputs(1) := timerB.io.full
interruptCtrl.io.inputs(2) := timerC.io.full
interruptCtrl.io.inputs(3) := timerD.io.full
io.interrupt := interruptCtrl.io.pendings.orR
}